#pragma once
#include <string>
#include <nlohmann/json.hpp>
#include <Common/headers.h>
#include <Graphics/color.h>


namespace occ {
	template<typename T>
	inline T max(T& a, T& b, T& c) {
		T& t = a > b ? a : b;
		return t > c ? t : c;
	}

	template<typename T>
	inline T min(T& a, T& b, T& c) {
		T& t = a < b ? a : b;
		return t < c ? t : c;
	}

	template<typename T>
	inline T clamp(T val, T _min, T _max) {
		if (val < _min) return _min;
		if (val > _max) return _max;
		return val;
	}

	/* project point to Normalized Device Coordinates, with x,y,z mapped to [-1,1] */
	inline vec4f toNDC(const mat4f& perspective, const vec4f& point) {
		vec4f pt = perspective * point;
		pt /= pt.w;
		return pt;
	}

	/* clipping to the frustum */
	inline bool inNDC(const vec3f& p) {
		if (
			-1 <= p.x && p.x <= 1 &&
			-1 <= p.y && p.y <= 1 &&
			-1 <= p.z && p.z <= 1
			)
		{
			return true;
		}
		return false;
	}

	inline bool inNDC(const vec4f& p) {
		if (
			-1 <= p.x && p.x <= 1 &&
			-1 <= p.y && p.y <= 1 &&
			-1 <= p.z && p.z <= 1
			)
		{
			return true;
		}
		return false;
	}

	mat4f getPerspectiveMatrix(
		occFloat zNear = 1, occFloat zFar = INFINITY,
		occFloat fovy = PI / 3, int w = 1024, int h = 768
	) {

		occFloat c = 1.0 / tan(fovy / 2.0);
		occFloat a = w / h;
		occFloat a33 = -1, a34 = -2 * zNear;
		if (isfinite(zFar)) {
			occFloat div = zNear - zFar;
			a33 = (zNear + zFar) / div;
			a34 = (2 * zFar * zNear) / div;
		}
		/*
		return mat4f(
			c/a/,   0,   0,   0,0
			  0,   c,   0,   0, 0
			  0,   0, a33, a34, -2
			  0,   0,  -1,   0  1
		);*/
		//return glm::perspective(fovy, a, zNear, zFar);
		// column major
		return mat4f(
			c / a, 0, 0, 0,
			0, c, 0, 0,
			0, 0, a33, -1,
			0, 0, a34, 0
		);
	}

	void from_json(const nlohmann::json& jvec, Color& color) {
		if (jvec.type() == nlohmann::detail::value_t::string) {
			color = DefaultColor::get(jvec);
			return;
		}
		color.r = (unsigned char)clamp(int(jvec[0]), 0, 255);
		color.g = (unsigned char)clamp(int(jvec[1]), 0, 255);
		color.b = (unsigned char)clamp(int(jvec[2]), 0, 255);
	}

	void from_json(const nlohmann::json& jvec, vec3f& vec) {
		vec.x = occFloat(jvec[0]);
		vec.y = occFloat(jvec[1]);
		vec.z = occFloat(jvec[2]);
	}

	class ModelDescription {
	public:
		std::string name;
		std::string path;
		Color color;
		vec3f scale, rotate, translate;
		ModelDescription() 
			:color(DefaultColor::Yellow()),
			scale(1, 1, 1), rotate(0, 0, 0), translate(0, 0, 0) {}
		
		friend void from_json(const nlohmann::json& j, ModelDescription& m) {
			j.at("name").get_to(m.name);
			j.at("path").get_to(m.path);
			if (j.contains("color")) {
				from_json(j["color"], m.color);
			}
			if (j.contains("scale")) {
				from_json(j["scale"], m.scale);
			}
			if (j.contains("rotate")) {
				auto& vec = j["rotate"];
				m.rotate.x = glm::radians(occFloat(vec[0]));
				m.rotate.y = glm::radians(occFloat(vec[1]));
				m.rotate.z = glm::radians(occFloat(vec[2]));
			}
			if (j.contains("translate")) {
				from_json(j["translate"], m.translate);
			}
		}
	};

	struct CameraDescription {
		vec3f position = vec3f(0, 0, 0);
		occFloat fovy = 60.0;
		occFloat zNear = 0.1;
		occFloat zFar = 1000.0;

		friend void from_json(const nlohmann::json& j, CameraDescription& m) {
			if (j.contains("position")) from_json(j["position"], m.position);
			if (j.contains("fovy")) { 
				occFloat fovy = glm::radians(occFloat(j["fovy"]));
				m.fovy = fovy;
			}
			if (j.contains("zNear")) j["zNear"].get_to(m.zNear);
			if (j.contains("zFar")) j["zFar"].get_to(m.zFar);
		}
	};

}